package controller

import (
    "sync"
)

type Queue struct {
    newJob              *sync.Cond
    files               []*JobFile
    jobsCountByPriority map[int]int
}

func NewQueue() *Queue {
    return &Queue{
        newJob:              sync.NewCond(new(sync.Mutex)),
        files:               make([]*JobFile, 0),
        jobsCountByPriority: make(map[int]int),
    }
}

func (q *Queue) Enqueue(f *JobFile) {
    q.newJob.L.Lock()
    defer q.newJob.L.Unlock()
    higher := 0
    for p, t := range q.jobsCountByPriority {
        if p >= f.Job.Priority {
            higher += t
        }
    }
    q.files = append(q.files[:higher], append([]*JobFile{f}, q.files[higher:]...)...)
    q.jobsCountByPriority[f.Job.Priority] += 1
    q.newJob.Signal()
}

func (q *Queue) EnqueueFront(f *JobFile) {
    q.newJob.L.Lock()
    defer q.newJob.L.Unlock()
    pos := 0
    for p, t := range q.jobsCountByPriority {
        if p > f.Job.Priority {
            pos += t
        }
    }
    q.files = append(q.files[:pos], append([]*JobFile{f}, q.files[pos:]...)...)
    q.jobsCountByPriority[f.Job.Priority] += 1
    q.newJob.Signal()
}

func (q *Queue) Dequeue(done chan struct{}) *JobFile {
    q.newJob.L.Lock()
    defer q.newJob.L.Unlock()
wait:
    if len(q.files) == 0 {
        waitC := make(chan struct{})
        go func() {
            q.newJob.Wait()
            close(waitC)
        }()
        select {
        case <-done:
            // let wait exit
            q.newJob.Broadcast()
            <- waitC
            return nil
        case <-waitC:
        }

    }
    if len(q.files) == 0 {
        goto wait
    }
    f := q.files[0]
    q.files = q.files[1:]
    q.jobsCountByPriority[f.Job.Priority] -= 1
    return f
}
